package daybook.model.transaction;

import daybook.Resources;
import daybook.exception.ValidationException;
import daybook.model.Book;
import daybook.model.Commodity;
import daybook.model.Comparators;
import org.immutables.value.Value;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;

@Value.Immutable
public abstract class Transaction {
    public abstract LocalDate getDate();
    public abstract Set<TransactionEntry> getEntries();

    @Value.Default
    public int getOrder() {
        return 0;
    }

    @Value.Default
    public String getDescription() {
        return "";
    }

    @Value.Default
    public boolean isChecked() {
        return false;
    }

    public boolean isBalanced() {
        final BigDecimal totalAmount = getEntries().stream()
                .map(TransactionEntry::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        return totalAmount.signum() == 0;
    }

    // 不需要外部信息的校验
    public void validate() {
        validate(false);
    }

    private void validate(boolean onlySelf) {
        if (getEntries().size() < 2) {
            throw ValidationException.of(Resources.ErrorMessages.HAVING_LESS_THAN_TWO_ENTRIES, toPrettyString());
        }

        if (!isBalanced()) {
            throw ValidationException.of(Resources.ErrorMessages.UNBALANCED_TRANSACTION, toPrettyString());
        }

        if (!onlySelf) {
            for (TransactionEntry transactionEntry : getEntries()) {
                transactionEntry.validate();
            }
        }
    }

    // 需要外部信息的校验
    public void validate(Commodity defaultCurrency) {
        validate(true);
        for (TransactionEntry transactionEntry : getEntries()) {
            transactionEntry.validate(defaultCurrency);
        }
    }

    static NavigableSet<TransactionEntry> sortItems(Collection<TransactionEntry> transactionEntries) {
        if (transactionEntries instanceof NavigableSet &&
                Comparators.itemComparator.equals(((NavigableSet<TransactionEntry>) transactionEntries).comparator())) {
            return (NavigableSet<TransactionEntry>) transactionEntries;
        } else {
            final TreeSet<TransactionEntry> result = new TreeSet<>(Comparators.itemComparator);
            result.addAll(transactionEntries);
            return result;
        }
    }

    private String toPrettyString(Function<TransactionEntry, String> toString) {
        final StringBuilder sb = new StringBuilder();
        sb.append(getDate()).append(" ");
        if (isChecked()) {
            sb.append("* ");
        }
        sb.append("\"").append(getDescription()).append("\"")
                .append(System.lineSeparator());

        for (TransactionEntry transactionEntry : getEntries()) {
            sb.append(toString.apply(transactionEntry)).append(System.lineSeparator());
        }
        return sb.toString();
    }

    public String toPrettyString(Book book) {
        return toPrettyString(item -> item.toPrettyString(book));
    }

    public String toPrettyString() {
        return toPrettyString(TransactionEntry::toPrettyString);
    }
}
